package spindles.api.domain;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.measure.quantities.Duration;
import javax.measure.quantities.Scalar;
import javax.measure.units.SI;

import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.time.DurationFormatUtils;

import spindles.api.util.ErrorMessages;
import spindles.api.util.UserException;

import com.sleepycat.persist.model.Persistent;


@Persistent
public class Interval extends DomainObject{
	
	/**
	 * 
	 */
	private transient static final long serialVersionUID = 9025173873343105786L;
	
	private transient final SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");

	private Date start;
	
	private Date end;
	
	public Interval(){
		super();
	}
	
	public Interval(Date day, String fromTime, String toTime) throws UserException{
		try {
			Calendar from = Calendar.getInstance();
			from.setTime(timeFormat.parse(fromTime));
			
			Calendar to = Calendar.getInstance();
			to.setTime(timeFormat.parse(toTime));
			
			day = DateUtils.truncate(day, Calendar.DAY_OF_MONTH);
			
			Calendar startCal = Calendar.getInstance();
			startCal.setTime(day);
			startCal.set(Calendar.HOUR_OF_DAY, from.get(Calendar.HOUR_OF_DAY));
			startCal.set(Calendar.MINUTE, from.get(Calendar.MINUTE));
			startCal.set(Calendar.SECOND, from.get(Calendar.SECOND));
			
			Calendar endCal = Calendar.getInstance();
			endCal.setTime(day);
			endCal.set(Calendar.HOUR_OF_DAY, to.get(Calendar.HOUR_OF_DAY));
			endCal.set(Calendar.MINUTE, to.get(Calendar.MINUTE));
			endCal.set(Calendar.SECOND, to.get(Calendar.SECOND));
			
			this.start = startCal.getTime();
			this.end = endCal.getTime();
			
			validate();
			
		} catch (ParseException e) {
			throw new UserException(ErrorMessages.TIME_UNEXPECTED_FORMAT);
		}		
	}
	
	public Interval(Date start, Date end){
		super();
		this.start = start;
		this.end = end;
		validate();
	}
	
	public int compareTo(Object o) {
		return CompareToBuilder.reflectionCompare(this, o, false, Interval.class);
	}
	
	public boolean equals(Object obj) {
		return EqualsBuilder.reflectionEquals(this, obj, false, Interval.class);
	}
	
	public int hashCode() {
		return HashCodeBuilder.reflectionHashCode(17, 
				37, this, false, Interval.class);
	}
	
	public boolean intersect(Interval that){
		if(this.contains(that.getStart()) || this.contains(that.getEnd())){
			return true;
		}
		
		if(this.contains(that)){
			return true;
		}
		
		if(that.contains(this)){
			return true;
		}
		
		return false;
	}
	
	public boolean contains(Interval that){		
		return (this.contains(that.getStart()) && this.contains(that.getEnd()));
	}
	
	public boolean contains(Date timePoint){		
		return ((getStart().compareTo(timePoint) <= 0)
				&& (getEnd().compareTo(timePoint) >= 0));
	}

	public Date getEnd() {
		return end;
	}
	
	public Scalar<Duration> gap(Interval that){
		long gap = that.getStart().getTime() - this.getEnd().getTime();
		return new Scalar<Duration>(gap/1000.0, SI.SECOND);
	}
	
	public Scalar<Duration> duration(){
		return new Scalar<Duration>((end.getTime() - start.getTime())/1000.0, 
				SI.SECOND);
	}
	
	public String durationText(){
		return DurationFormatUtils.formatDurationHMS(duration().intValue());
	}
	
	public String getEndTime() {
		return dateToString(getEnd());
	}

	public void setEnd(Date end) {
		this.end = end;
		validate();
	}

	public Date getStart() {
		return start;
	}
	
	public String getStartTime() {
		return dateToString(getStart());
	}
	
	public String toPrettyString(){
		return getStartTime() + " - " + getEndTime();
	}
	
	protected String dateToString(Date date){
		return DateFormatUtils.ISO_TIME_NO_T_FORMAT.format(date);
	}

	public void setStart(Date start) {
		this.start = start;
	}
	
	protected void validate(){
		Validate.isTrue(start.getTime() <= end.getTime(), "End should be greater or equal to start.");
	}	
	

}
